﻿using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Linq;
using System.Text;
using System.Drawing;
using GRV11;

namespace gpuimp.core
{
    public enum ToolType
    {
        SelectLasso,
        SelectRectangle,
        SelectEllipse,
        SelectArea,
        Paintbucket,
        Gradient,
        Brush,
        Eraser,
        Text,
        Zoom,
        Blur
    }
    public class Tool
    {
        protected String fragProg;
        protected String vertProg;
        protected Bitmap icon;
        protected int id;
        protected String name;
        //won't compile until you have key and value...
        //Dictionary<string> setting;
        protected int shader;
        public static int copyShader;
        protected bool applying;
        public ToolType type;

        private float minSize = 1;
        private float maxSize = 10;
        private float minOpacity = 1;
        private float maxOpacity = 100;
        private float minHardness = 1;
        private float maxHardness = 100;
        private float minScatter = 1;
        private float maxScatter = 100;

        //settings the gui can set
        public float size = 1;
        public float opacity = 1;
        public float hardness = 100;
        public float scatter = 0;
        public float spacing = 1;

        //shader to copy texture back over
        public static string copyVertProg = @"
varying vec2 texCoord;

void main(void)
{
   gl_Position = ftransform();
   texCoord = gl_MultiTexCoord0.xy;
}";

        public static string copyFragProg = @"
uniform sampler2D diffuseMap;
varying vec2 texCoord;

void main(void)
{
    vec4 color = texture2D(diffuseMap, texCoord);
    gl_FragColor = color;
}";

        /// <summary>
        /// basic tool constructor
        /// </summary>
        public Tool()
        {
            fragProg = null;
            vertProg = null;
            icon = null;
            id = -1;
            name = null;
            shader = -1;
            applying = false;

            //forced to paintbrush shader for the time being
            setFragProg("");
            setVertProg("");
            setupShader();
            ToolManager.addTool(this);
        }

        /// <summary>
        /// create a tool with specified setting already
        /// </summary>
        /// <param name="tool"></param>
        public Tool(Tool tool)
        {
            this.fragProg = tool.fragProg;
            this.vertProg = tool.vertProg;
            this.icon = tool.icon;
            this.id = tool.id;
            this.name = tool.name;
            this.shader = tool.shader;
            this.applying = false;
            setVertProg("");
            setFragProg("");
            setupShader();
            ToolManager.addTool(this);
        }

        public ToolType Type
        {
            get { return type; }
            set { type = value; }
        }

        public void setupShader()
        {
            // Load shader
            if (true == CanvasManager.gr.bglCreateProgramObjectARB)
            {
                GRSupport.SupportShaderProgramCreate
                (
                    CanvasManager.gr,
                    vertProg,
                    fragProg,
                    ref this.shader
                );

                GRSupport.SupportShaderProgramCreate
                (
                    CanvasManager.gr,
                    copyVertProg,
                    copyFragProg,
                    ref copyShader
                );
            }
        }

        protected virtual void setToolShader(PointF position, List<PointF> pastPositions, PointF viewSize, System.Windows.Media.Color color, float pressure)
        {
            GRSupport.SupportShaderProgramSelect(CanvasManager.gr, this.shader);

            int shaderDiffuse = (int)CanvasManager.gr.glGetUniformLocationARB(this.shader, "diffuseMap");
            int shaderCursor = (int)CanvasManager.gr.glGetUniformLocationARB(this.shader, "cursorPos");
            int shaderImage = (int)CanvasManager.gr.glGetUniformLocationARB(this.shader, "imageSize");
            int shaderViewport = (int)CanvasManager.gr.glGetUniformLocationARB(this.shader, "viewportSize");
            int shaderCanvas = (int)CanvasManager.gr.glGetUniformLocationARB(this.shader, "canvasPos");
            int shaderColor = (int)CanvasManager.gr.glGetUniformLocationARB(this.shader, "brushColor");
            int shaderBrushsize = (int)CanvasManager.gr.glGetUniformLocationARB(this.shader, "brushSize");
            int shaderBrushhardness = (int)CanvasManager.gr.glGetUniformLocationARB(this.shader, "brushHardness");
            CanvasManager.gr.glUniform2fARB(shaderCursor, position.X, position.Y);
            CanvasManager.gr.glUniform2fARB(shaderImage, CanvasManager.CurrentCanvas.ImageSize.X, CanvasManager.CurrentCanvas.ImageSize.Y);
            CanvasManager.gr.glUniform2fARB(shaderViewport, viewSize.X, viewSize.Y);
            CanvasManager.gr.glUniform2fARB(shaderCanvas, 0, 0); //moving the canvas would go here
            CanvasManager.gr.glUniform1fARB(shaderBrushsize, 10 * pressure);
            CanvasManager.gr.glUniform1fARB(shaderBrushhardness, 100);
            CanvasManager.gr.glUniform4fARB(shaderColor, color.R, color.G, color.B,1);
            CanvasManager.gr.glUniform1fARB(shaderDiffuse, 0);

        }

        public void setCopyShader()
        {
            GRSupport.SupportShaderProgramSelect(CanvasManager.gr, copyShader);

            int shaderDiffuse = (int)CanvasManager.gr.glGetUniformLocationARB(copyShader, "diffuseMap");
            CanvasManager.gr.glUniform1iARB(shaderDiffuse, 0);
        }

        public void setupToolViewport(GR gr)
        {
            // Viewport 
            Point imageSize = CanvasManager.CurrentCanvas.ImageSize;

            // Viewport to same size as the texture
            gr.glViewport(0, 0, imageSize.X, imageSize.Y);

            // Clear the viewport
            gr.glClear(GR.GL_COLOR_BUFFER_BIT | GR.GL_DEPTH_BUFFER_BIT);

            // PROJECTION matrix
            gr.glMatrixMode(GR.GL_PROJECTION);
            gr.glLoadIdentity();

            int halfWidth = imageSize.X;// / 2;
            int halfHeight = imageSize.Y;// / 2;
            gr.gluOrtho2D(-imageSize.X, imageSize.X, -imageSize.Y, imageSize.Y);

            // MODELVIEW matrix, typically used to transform individual models
            gr.glMatrixMode(GR.GL_MODELVIEW);
            gr.glLoadIdentity();
        }

        public virtual void draw(GRControl control, PointF position, List<PointF> pastPositions, System.Windows.Media.Color color, float pressure)
        {
            applying = true;

            //get render control
            GR gr = control.GetGR();
            //height and width of the image
            int activeLayer = CanvasManager.CurrentCanvas.LayerManager.ActiveLayer;
            int width = CanvasManager.CurrentCanvas.LayerManager.Layers[activeLayer].grBaseTexture.GetWidth();
            int height = CanvasManager.CurrentCanvas.LayerManager.Layers[activeLayer].grBaseTexture.GetHeight();
            
            gr.glLoadIdentity();
            //gr.glViewport(0, 0, width, height);
            
            gr.glPushMatrix();
            //make a new texture - just testing this way

            //set the Tool shader
            setToolShader(position, pastPositions, new PointF(control.Width, control.Height), color, pressure);
            //clear render
            gr.glClear(GR.GL_COLOR_BUFFER_BIT);
            //get IDs of the two textures to use
            //int fboTexture = texture.GetOpenGLTextureID();// CanvasManager.CurrentCanvas.grFBOTexture.GetOpenGLTextureID();
            int fboTexture = CanvasManager.CurrentCanvas.grFBOTexture.GetOpenGLTextureID();
            int layerTexture = CanvasManager.CurrentCanvas.LayerManager.Layers[activeLayer].grBaseTexture.GetOpenGLTextureID();

            //bind FBO
            gr.glBindFramebufferEXT(GR.GL_FRAMEBUFFER_EXT, CanvasManager.CurrentCanvas.FBO[0]);
            //bind intermediate texture to fbo
            gr.glFramebufferTexture2DEXT(GR.GL_FRAMEBUFFER_EXT, GR.GL_COLOR_ATTACHMENT0_EXT, GR.GL_TEXTURE_2D, fboTexture, 0);

            //set to draw to color 0 buffer
            int[] buffers = { GR.GL_COLOR_ATTACHMENT0_EXT };
            gr.glDrawBuffers(1, buffers);

            //set to render to texture size
            setupToolViewport(gr);

            //set the original texture active
            CanvasManager.CurrentCanvas.LayerManager.Layers[activeLayer].grBaseTexture.SetAsActiveTexture(gr);
            //draw quad with this texture
            CanvasManager.CurrentCanvas.LayerManager.Layers[activeLayer].Draw(gr, false, false);

            //unbind FBO
            CanvasManager.gr.glBindFramebufferEXT(GR.GL_FRAMEBUFFER_EXT, 0);
            //remove shader
            GRSupport.SupportShaderProgramSelect(CanvasManager.gr, 0);
            
            //rebind fbo
            gr.glBindFramebufferEXT(GR.GL_FRAMEBUFFER_EXT, CanvasManager.CurrentCanvas.FBO[1]);
            //bind final output texture
            gr.glFramebufferTexture2DEXT(GR.GL_FRAMEBUFFER_EXT, GR.GL_COLOR_ATTACHMENT0_EXT, GR.GL_TEXTURE_2D, layerTexture, 0);
            //switch to copy texture
            setCopyShader();
            //render wuad with intermediate texture
            //set the intermediate texture active
            //texture.SetAsActiveTexture(gr);
            CanvasManager.CurrentCanvas.grFBOTexture.SetAsActiveTexture(gr);
            CanvasManager.CurrentCanvas.LayerManager.Layers[activeLayer].Draw(CanvasManager.gr, false, false);

             //unbind FBO
             CanvasManager.gr.glBindFramebufferEXT(GR.GL_FRAMEBUFFER_EXT, 0);
             //remove shader
             GRSupport.SupportShaderProgramSelect(CanvasManager.gr, 0);
            
             gr.glPopAttrib();
             gr.glPopMatrix();
        }

        public virtual void setFragProg(String frag)
        {
         //   this.fragProg = fragProg;
            this.fragProg = @"
//existing texture
uniform sampler2D diffuseMap;
//position of cursor
uniform vec2 cursorPos;
//total size of image
uniform vec2 imageSize;
//size of viewport
uniform vec2 viewportSize;
//offset of viewport in image
uniform vec2 canvasPos;
//size of the brush - probably not accurate just yet
uniform float brushSize;
uniform float brushHardness;
//color to draw with
uniform vec4 brushColor;
//uv coordinate of quad image is applied to
varying vec2 texCoord;

void main(void)
{
    //get the existing texture
    vec4 diffuse = texture2D(diffuseMap, texCoord);

    //we have to translate the position of the cursor into position relative to 
    //the uv coordinates of the quad the layer texture is applied to
    //percent of the image the viewport covers
    vec2 viewportOffset = viewportSize / imageSize;
    //viewportOffset.y = 1 - viewportOffset.y;
    
    //position of the cursor relative to the viewport
    vec2 cursorOffset = cursorPos / viewportSize;
    cursorOffset.y = 1 - cursorOffset.y;

    //cursor position relative to image
    vec2 cursor = cursorOffset * viewportOffset;

    //flip the y coordinate since uv's and position vectors are opposite each other
   // cursor.y = 1 - cursor.y;    
    
    //convert brush size to texcoord coordinate space
    brushSize /= imageSize;
    
    vec2 aspectRatio = vec2(1, imageSize.x / imageSize.y);
    float dist = length( (cursor - texCoord) / aspectRatio);
    dist = min(dist, 1.0);
    float amount = 0;
    
    dist = 1 - dist + brushSize;
    amount = pow(dist, brushHardness * 15); //15 is a magic number to make 100 be a hard brush
    amount *= brushColor.a;
    amount = clamp(amount, 0.0, 1.0);
    gl_FragColor = (1 - amount) * diffuse + amount * brushColor;
}  
";

        }

        public String getFragProg()
        {
            return (this.fragProg);
        }

        public virtual void setVertProg(String vert)
        {
            //this.vertProg = vertProg;
            vertProg = @"varying vec2  texCoord;

void main(void)
{
   gl_Position = ftransform();
   texCoord = gl_MultiTexCoord0.xy;
}";
        }

        public String getVertProg()
        {
            return (this.vertProg);
        }

        public void setIcon(Bitmap icon)
        {
            this.icon = icon;
        }

        public Bitmap getIcon()
        {
            return (this.icon);
        }

        public void setID(int id)
        {
            this.id = id;
        }

        public int getID()
        {
            return (this.id);
        }

        public void setName(String name)
        {
            this.name = name;
        }

        public String getName()
        {
            return (this.name);
        }

        public void setShader(int shader)
        {
            this.shader = shader;
        }

        public int getShader()
        {
            return (this.shader);
        }

        public bool getApplying()
        {
            return this.applying;
        }

        public void setApplying(bool newAp)
        {
            this.applying = newAp;
        }

        #region Size setters and getters
        public void setMinSize(float minSize)
        {
            this.minSize = minSize;
        }
        public void setMaxSize(float maxSize)
        {
            this.maxSize = maxSize;
        }
        public void setSize(float size)
        {
            if (size < this.minSize)
                this.size = this.minSize;
            if (size > this.maxSize)
                this.size = this.maxSize;
            else
                this.size = size;
        }
        public float getSize()
        {
            return (this.size);
        }
        #endregion

        #region opacity setters and getters
        public void setMinOpacity(float minOpacity)
        {
            this.minOpacity = minOpacity;
        }
        public void setMaxOpacity(float maxOpacity)
        {
            this.maxOpacity = maxOpacity;
        }
        public void setOpacity(float opacity)
        {
            if (opacity < minOpacity)
                this.opacity = minOpacity;
            if (opacity > maxOpacity)
                this.opacity = maxOpacity;
            else
                this.opacity = opacity;
        }
        public float getOpacity()
        {
            return (this.opacity);
        }
        #endregion

        #region hardness setters and getters
        public void setMinHardness(float minHardness)
        {
            this.minHardness = minHardness;
        }
        public void setMaxHardness(float maxHardness)
        {
            this.maxHardness = maxHardness;
        }
        public void setHardness(float hardness)
        {
            if (hardness < minHardness)
                this.hardness = minHardness;
            if (hardness > maxHardness)
                this.hardness = maxHardness;
            else
                this.hardness = hardness;
        }
        public float getHardness()
        {
            return (this.hardness);
        }
        #endregion

        #region scatter setters and getters
        public void setMinScatter(float minScatter)
        {
            this.minScatter = minScatter;
        }
        public void setMaxScatter(float maxScatter)
        {
            this.maxScatter = maxScatter;
        }
        public void setScatter(float scatter)
        {
            if (scatter < minScatter)
                this.scatter = minScatter;
            if (scatter > maxScatter)
                this.scatter = maxScatter;
            else
                this.scatter = scatter;
        }
        public float getScatter()
        {
            return (this.scatter);
        }
        #endregion

    }
}
